﻿package com.bizmlib.ui.composite
{
	import flash.events.*;
	import com.bizmlib.utils.array.ArrayHandling;
	
	
	[Event(name="showStart", type="com.bizmlib.ui.composite.UICompositeManagerEvent")]
	[Event(name="hideStart", type="com.bizmlib.ui.composite.UICompositeManagerEvent")]
	[Event(name="hideComplete", type="com.bizmlib.ui.composite.UICompositeManagerEvent")]
	[Event(name="showComplete", type="com.bizmlib.ui.composite.UICompositeManagerEvent")]
	[Event(name="itemsRemoved", type="com.bizmlib.ui.composite.UICompositeManagerEvent")]
	
	
	/**
	 * STATE_READY	- manager is ready to show items. queuedItems and visibleItems lists are empty. 
	 * STATE_SHOW	- manager is performing the show operation at the moment. All the items that has to be shown are in the queuedItems list. When item is shown it goes to the
	 * visibileItems list. When queuedItems is empty and all the items has reported about the show completion the manager switches to the STATE_SHOWN. From te STATE_SHOW manager can also
	 * switch to the STATE_HIDE if hide method was invoked. In that case all the items are removed from the queuedItems list and manager hides all the items which are listed in the
	 * visibleItems list. In case the show method was invoked during the STATE_SHOW state, then there are two ways manager can perform. One way is to clean all the currently shown items,
	 * in other words to hide everything and show new items from an empty state. Another way is to add all the new items to the queuedItems list and continue the current show operation.
	 * Check the show method's comments for more details.
	 * STATE_SHOWN	- the show operation was completed successfully. Similarly to the STATE_SHOW a hide operation can be started straight away. The show call performs in the same way as
	 * in the STATE_SHOW state. The only difference is that the manager switches to the STATE_SHOW, in case new items should be shown without going through the hide state.
	 * STATE_HIDE	- manager hides all the items that has been shown, in other words all the items that are in the visibleItems list. Before the hide operation is started manager can
	 * also clean the queuedItems list, in this case after the hide completion manager switches to the STATE_READY state. In case queuedItems list is not cleared the manager after the
	 * hide operation is completed starts a show operation straight-away and switches to the STATE_SHOW state.
	 */
	public class UICompositeManager extends EventDispatcher
	{
		
		/**
		 * States of the UICompositeManager:
		 * STATE_READY	- nothing is shown. Manager gets into this state either at the very beginning or after the hide operation is completed. From this point show operation can start straight away.
		 * STATE_SHOW	- show operation is in progress. Manager goes through all the items in the queuedItems list, and shows them one-by-one. Show operation can be interrupted by the hide operation.
		 * STATE_SHOWN	- show operation has been successfully completed. At this point manager can invoke hide operation. Also a show operation can be started if there are extra items to show.
		 * STATE_HIDE	- hide operation is in progress. No other action can be started at this point. If show method is invoked all the items go to the queuedItems list and wait for the hide completion.
		 */
		protected static const STATE_READY:Number = 1;
		protected static const STATE_SHOW:Number = 2;
		protected static const STATE_SHOWN:Number = 3;
		protected static const STATE_HIDE:Number = 4;
		
		protected var state:Number;
		protected var visibleItems:Array;
		protected var queuedItems:Array;
		protected var queuedItemsProperties:Array;
		protected var showItems:Array;
		protected var hideItems:Array;
		protected var removeItems:Array;
		
		
		public function UICompositeManager()
		{
			init();
		}
		
		
		public function get visibleItemsCount():Number
		{
			return visibleItems.length;
		}
		public function get allShowItems():Array
		{
			return (new Array()).concat(queuedItems).concat(showItems).concat(visibleItems);
		}
		public function get allShowItemsCount():Number
		{
			return allShowItems.length;
		}
		public function get queuedItemsPropertiesCopy():Array
		{
			var arr:Array = queuedItemsProperties ? queuedItemsProperties.concat() : new Array();
			
			arr.forEach(
				function(item:*, index:int, array:Array):Boolean
				{
					if(item && item.properties && item.properties is Object)
					{
						var itemCopy:Object = new Object();
						for(var propertyName:String in item.properties)
						{
							itemCopy[propertyName] = item.properties[propertyName];
						}
						array[index] = itemCopy;
					}
					return true;
				},
				null
			);
			
			return arr;
		}
		
		
		/**
		 * Starts the show operation.
		 */
		public function show(itemsToShow:Array, doNotRestart:Boolean = false):void
		{
			//trace(this + ".show(" + arguments + ")");
			//trace((new Error()).getStackTrace());
			//trace("state=" + state);
			
			doNotRestart = doNotRestart || false;
			
			var ACTION_DO_NOTHING:Number = 0;
			var ACTION_START_SHOW:Number = 1;
			var ACTION_START_HIDE:Number = 2;
			var localAction:Number = ACTION_DO_NOTHING;
			
			switch(state)
			{
				case STATE_READY:
					localAction = ACTION_START_SHOW;
					break;
				case STATE_SHOW:
				case STATE_SHOWN:
					if(doNotRestart)
					{
						localAction = ACTION_START_SHOW;
					}
					else
					{
						localAction = ACTION_START_HIDE;
						clearQueue();
					}
					break;
				case STATE_HIDE:
					if(!doNotRestart)
					{
						clearQueue();
					}
					localAction = ACTION_DO_NOTHING;
					break;
			}
			
			//var localItemsToShow:Array = ArrayHandling.difference(itemsToShow, (new Array()).concat(visibleItems, showItems, queuedItems));
			//parseShowArguments.apply(this, localItemsToShow, doNotRestart);
			parseItemsToShowList(itemsToShow, (doNotRestart && (localAction==ACTION_START_SHOW)));
			
			//trace("localAction=" + localAction);
			
			switch(localAction)
			{
				case ACTION_DO_NOTHING:
					break;
				case ACTION_START_SHOW:
					startShow();
					break;
				case ACTION_START_HIDE:
					/**
					 * the line below was changed so, that hide always receive 'true' as a doNotClearQueue parameter, because queuedItems list was filled by a
					 * parseItemsToShowList call, and contans all the items that needs to be shown after a hide operation is completed
					 */
					//hide(undefined, doNotRestart);
					hide(undefined, true);
					break;
			}
		}
		/**
		 * Starts a hide operation.
		 * @param	itemsList			a list of the items, this parameter is actually intended to set an order, how the items should hide; it's possible that the order of the items
		 *								in visibleItems list doesn't fit the one you have externally, so you can correct it by passing this itemsList
		 * @param	doNotClearQueue		specifies whether the queuedItems list should be cleared or not, if you don't clear the queue then the show operation will start automatically after the
		 *								hide has completed
		 */
		public function hide(itemsList:Array = undefined, doNotClearQueue:Boolean = false):void
		{
			//trace(this + ".hide(" + arguments + ")");
			//trace((new Error()).getStackTrace());
			//trace("state=" + state);
			
			doNotClearQueue = doNotClearQueue || false;
	
			if(state==STATE_READY)
			{
				return;
			}
	
			if(!doNotClearQueue)
			{
				clearQueue();
			}
			
			//TODO: a if block below must be doublechecked
			if(state==STATE_HIDE && !itemsList)
			{
				return;
			}
			
			startHide(itemsList);
		}
		public function remove(items:Array, removeImmediatelly:Boolean = false):void
		{
			//trace(this + ".remove(" + arguments + ")");
			for(var i:Number=0; i<items.length; i++)
			{
				var itemIndex:Number;
				var item:Object;
				
				if((itemIndex = ArrayHandling.getItemIndex(queuedItems, items[i]))+1)
				{
					var localRemovedItems:Array = queuedItems.splice(itemIndex, 1);
					var propertiesIndex:Number = getItemPropertiesIndex(IUICompositeItem(localRemovedItems[0]));
					if(!isNaN(propertiesIndex))
					{
						//queuedItemsProperties.splice(propertiesIndex, 1);
						removePropertiesContainer(propertiesIndex);
					}
					dispatchRemoveItemsEvent(localRemovedItems);
				}
				else if((itemIndex = ArrayHandling.getItemIndex(showItems, items[i]))+1)
				{
					item = showItems[itemIndex];
					showItems.splice(itemIndex, 1);
					removeItems.push(item);
					itemShowComplete(item as IUICompositeItem);
					if(removeImmediatelly)
					{
						itemHideComplete(item as UICompositeItem);
					}
					else
					{
						item.hide();
					}
				}
				else if((itemIndex = ArrayHandling.getItemIndex(visibleItems, items[i]))+1)
				{
					item = visibleItems[itemIndex];
					visibleItems.splice(itemIndex, 1);
					removeItems.push(item);
					if(removeImmediatelly)
					{
						itemHideComplete(item as UICompositeItem);
					}
					else
					{
						item.hide();
					}
				}
				else if((itemIndex = ArrayHandling.getItemIndex(hideItems, items[i]))+1)
				{
					item = hideItems[itemIndex];
					hideItems.splice(itemIndex, 1);
					removeItems.push(item);
					if(removeImmediatelly)
					{
						itemHideComplete(item as UICompositeItem);
					}
					else
					{
						item.hide();
					}
				}
				else
				{
					dispatchRemoveItemsEvent([items[i]]);
				}
			}
		}
		public function clean():void
		{
			cleanList(visibleItems);
			visibleItems = null;
			
			cleanList(queuedItems);
			queuedItems = null;
			
			cleanList(hideItems);
			hideItems = null;
			
			cleanList(showItems);
			showItems = null;
			
			cleanList(removeItems);
			removeItems = null;
			
			while(queuedItemsProperties.length>0)
			{
				removePropertiesContainer(0);
			}
		}
		private function cleanList(list:Array):void
		{
			while(list.length>0)
			{
				var uiItem:IUICompositeItem = list.pop() as IUICompositeItem;
				uiItem.removeEventListener(UICompositeItemEvent.SHOW_COMPLETE, itemShowCompleteEventHandling);
				uiItem.removeEventListener(UICompositeItemEvent.HIDE_COMPLETE, itemHideCompleteEventHandling);
			}
		}
	
	
		protected function init():void
		{
			visibleItems = new Array();
			queuedItems = new Array();
			hideItems = new Array();
			showItems = new Array();
			removeItems = new Array();
			queuedItemsProperties = new Array();
	
			state = STATE_READY;
		}
		
		protected function getItemPropertiesIndex(item:IUICompositeItem):Number
		{
			for(var i:Number=0; i<queuedItemsProperties.length; i++)
			{
				if(queuedItemsProperties[i].target==item)
				{
					return i;
				}
			}
			
			return Number.NaN;
		}
		protected function getItemPropertiesContainer(item:IUICompositeItem):Object
		{
			var index:Number = getItemPropertiesIndex(item);
			
			if(!isNaN(index))
			{
				return queuedItemsProperties[index];
			}
			else
			{
				return null;
			}
		}
		
		/*
		 * Parses the arguments passed to the show method. Is invoked only from the show method, but still this code was moved to a separate method for a better readability.
		 */
		protected function parseItemsToShowList(itemsToShow:Array, checkVisibleQueues:Boolean):void
		{
			//trace(this + ".parseItemsToShowList(" + arguments + ")");
			
			
			for(var i:Number=0; i<itemsToShow.length; i++)
			{
				var item:Object = itemsToShow[i];
				var uiItem:IUICompositeItem;
				
				//trace("item=" + item);
				
				if(!(item is IUICompositeItem) && item.properties)
				{
					var itemProperties:Object = item.properties;
					uiItem = IUICompositeItem(item.target);
					
					var propertiesContainer:Object;
					if((propertiesContainer = getItemPropertiesContainer(uiItem)))
					{
						propertiesContainer.properties = itemProperties;
					}
					else
					{
						propertiesContainer = {target:uiItem, properties:itemProperties};
						queuedItemsProperties.push(propertiesContainer);
					}
					
					//trace(queuedItemsProperties);
				}
				else
				{
					uiItem = IUICompositeItem(item);
				}
	
				if(!uiItem)
				{
					throw new Error("Error in " + this + ": found item(" + item + ") parameter that seems to be not a proper implementation of IUICompositeItem interface, "
						+ " 'show' call of " + this);
				}
				else if(ArrayHandling.isItemInArray(queuedItems, uiItem))
				{
					// item is already in a queuedList. currently we do nothing, just skip push operation
				}
				else if(checkVisibleQueues && (ArrayHandling.isItemInArray(visibleItems, uiItem) || ArrayHandling.isItemInArray(showItems, uiItem)))
				{
					// item is already shown is in the middle of performing show operation, so we just skip it
				}
				else
				{
					uiItem.addEventListener(UICompositeItemEvent.SHOW_COMPLETE, itemShowCompleteEventHandling);
					uiItem.addEventListener(UICompositeItemEvent.HIDE_COMPLETE, itemHideCompleteEventHandling);
					
					queuedItems.push(uiItem);
					/**/
				}
			}
		}
		protected function startShow():void
		{
			//trace(this + ".startShow(" + arguments + ")");
			
			var localDispatchEvent:Boolean = false;
			
			switch(state)
			{
				case STATE_SHOW:
					break;
				case STATE_READY:
					localDispatchEvent = true;
				case STATE_SHOWN:
					state = STATE_SHOW;
					break;
				default:
					return;
			}
			
			startShowImplementation();
			
			if(localDispatchEvent)
			{
				dispatchEvent(new UICompositeManagerEvent(UICompositeManagerEvent.SHOW_START));
			}
		}
		/**
		 * This is an implementation of the show method. Could vary of the UICompositeManager implementation. Here you should check if the show can be started, depending on your implementation.
		 */
		protected function startShowImplementation():void
		{
			var queuedItemsCopy:Array = queuedItems.concat();
			ArrayHandling.clearArray(queuedItems);
			
			while(queuedItemsCopy.length>0)
			{
				showItems.push(queuedItemsCopy.shift());
			}
			//trace("showItems=" + showItems);
			var showItemsCopy:Array = showItems.concat();
			for(var i:Number=0; i<showItemsCopy.length; i++)
			{
				//var item:Object = showItems[showItems.length-1];
				//showItem(IUICompositeItem(item));
				//IUICompositeItem(queuedItems[i]).show();
				showItem(IUICompositeItem(showItemsCopy[i]));
			}
		}
		protected function showItem(item:IUICompositeItem):void
		{
			//trace(this + ".showItem(" + arguments + ")");
			
			var propertiesIndex:Number = getItemPropertiesIndex(item);
			var propertiesContainer:Object = isNaN(propertiesIndex)?null:queuedItemsProperties[propertiesIndex];
			
			//trace("propertiesIndex=" + propertiesIndex + "\tpropertiesContainer=" + propertiesContainer);
			
			if(propertiesContainer)
			{
				for(var propertyName:String in propertiesContainer.properties)
				{
					//trace(propertyName + "\t" + propertiesContainer.properties[propertyName]);
					item[propertyName] = propertiesContainer.properties[propertyName];
				}
				//queuedItemsProperties.splice(propertiesIndex, 1);
				removePropertiesContainer(propertiesIndex);
			}
			
			item.show();
		}
		/**
		 * Not implemented. Should check if all the items has been shown and switch the state then.
		 */
		protected function itemShowCompleteEventHandling(event:Object):void
		{
			itemShowComplete(event.target as IUICompositeItem);
		}
		protected function itemShowComplete(item:IUICompositeItem):void
		{
			//trace(this + ".itemShowComplete(" + arguments + ")");
			
			if(state!=STATE_SHOW)
			{
				//throw new Error("Warning in " + this + ": itemShowComplete call while state is not STATE_SHOW, the coude should be checked.");
				return;
			}
			
			var itemIndex:Number = -1;
			
			if(ArrayHandling.isItemInArray(removeItems, item))
			{
			}
			else if((itemIndex = ArrayHandling.getItemIndex(showItems, item))>-1)
			{
				visibleItems.push(showItems[itemIndex]);
				showItems.splice(itemIndex, 1);
			}
			
			dispatchEvent(new UICompositeManagerEvent(UICompositeManagerEvent.ITEM_SHOW_COMPLETE, [item]));
			
			completeShow();
		}
		protected function completeShow():Boolean
		{
			//trace(this + ".completeShow(" + arguments + ")");
			
			if(showItems.length==0 && queuedItems.length==0)
			{
				//trace("yeahhh");
				
				state = STATE_SHOWN;
				
				dispatchEvent(new UICompositeManagerEvent(UICompositeManagerEvent.SHOW_COMPLETE));
				
				return true;
			}
			else
			{
				return false;
			}
		}
		
		protected function startHide(itemsList:Array = undefined):void
		{
			//trace(this + ".startHide(" + arguments + ")");
			//trace((new Error()).getStackTrace());
			
			if(state==STATE_HIDE)
			{
				return;
			}
			else
			{
				state = STATE_HIDE;
			}
			
			visibleItems = visibleItems.concat(showItems);
			showItems = null;
			showItems = new Array();
			
			if(itemsList)
			{
				var localBuffer:Object;
				var visibleListIndex:Number;
				for(var i:Number=0; i<itemsList.length; i++)
				{
					visibleListIndex = ArrayHandling.getItemIndex(visibleItems, itemsList[i]);
					if(visibleListIndex!=-1 && visibleListIndex!=i)
					{
						localBuffer = visibleItems[visibleListIndex];
						visibleItems[visibleListIndex] = visibleItems[i];
						visibleItems[i] = localBuffer;
					}
				}
			}
			
			if(isHideCompleted())
			{
				completeHide();
			}
			else
			{
				dispatchEvent(new UICompositeManagerEvent(UICompositeManagerEvent.HIDE_START));
				startHideImplementation();
				//dispatchEvent(new UICompositeManagerEvent(UICompositeManagerEvent.HIDE_START));
			}
		}
		protected function startHideImplementation():void
		{
			//trace(this + ".startHideImplementation(" + arguments + ")");
			
			var visibleItemsCopy:Array = visibleItems.concat();
			ArrayHandling.clearArray(visibleItems);
			
			while(visibleItemsCopy.length>0)
			{
				//var item:IUICompositeItem = IUICompositeItem(visibleItemsCopy.shift());
				//trace("item=" + item);
				//hideItems.push(item);
				hideItems.push(visibleItemsCopy.shift());
			}
			var hideItemsCopy:Array = hideItems.concat();
			for(var i:Number=0; i<hideItemsCopy.length; i++)
			{
				//trace(hideItemsCopy[i] + ".hide()");
				if(!IUICompositeItem(hideItemsCopy[i]).hide())
					itemHideComplete(hideItemsCopy[i]);
			}
		}
		protected function itemHideCompleteEventHandling(event:Object):void
		{
			itemHideComplete(event.target as IUICompositeItem);
		}
		protected function itemHideComplete(item:IUICompositeItem):void
		{
			//trace(this + ".itemHideComplete(" + arguments + ")");
			
			var itemIndex:Number = -1;
			
			if((itemIndex = ArrayHandling.getItemIndex(removeItems, item))>-1)
			{
				var localRemovedItems:Array = removeItems.splice(itemIndex, 1);
				dispatchRemoveItemsEvent(localRemovedItems);
			}
			else if((itemIndex = ArrayHandling.getItemIndex(hideItems, item))>-1)
			{
				hideItems.splice(itemIndex, 1);
			}
			
			if(state==STATE_HIDE)
			{
				completeHide();
			}
		}
		protected function isHideCompleted():Boolean
		{
			return (visibleItems.length==0 && hideItems.length==0);
		}
		protected function completeHide():Boolean
		{
			//trace(this + ".completeHide(" + arguments + ")");
			
			//if(visibleItems.length==0 && hideItems.length==0)
			if(isHideCompleted())
			{
				//trace("yeahhh");
				
				state = STATE_READY;
				
				var queueCopy:Array = queuedItems.concat();
				queuedItems = new Array();
				
				dispatchEvent(new UICompositeManagerEvent(UICompositeManagerEvent.HIDE_COMPLETE));
				
				//trace("queueCopy:" + queueCopy);
				
				if(queueCopy.length>0)
				{
					show(queueCopy);
				}
				
				return true;
			}
			else
			{
				return false;
			}
		}
		
		protected function dispatchRemoveItemsEvent(removedItems:Array):void
		{
			dispatchEvent(new UICompositeManagerEvent(UICompositeManagerEvent.ITEMS_REMOVED, removedItems));
		}
		
		protected function clearQueue():void
		{
			queuedItems = null;
			queuedItems = new Array();
		}
		
		private function removePropertiesContainer(index:Number):void
		{
			var propertiesContainer:Object = queuedItemsProperties[index];
			if(propertiesContainer)
			{
				propertiesContainer.target = null;
				propertiesContainer.properties = null;
			}
			queuedItemsProperties.splice(index, 1);
		}
		
		override public function toString():String
		{
			return "[UICompositeManager	state:" + state + "	visible:" + visibleItems.length + "	queued:" + queuedItems.length + "	items to show:" + showItems.length + "		items to hide:" + 
				hideItems.length + "		items to remove:" + removeItems.length + "]";
		}
	}
}